#pragma once

#include <stx/core/coro.hpp>

inline namespace stx
{
enum class ProbDist
{
    K_INTEGRAL_UNIFORM,
    K_REAL_UNIFORM,
    K_NORMAL
};

[[nodiscard]] PodBuffer<std::random_device::result_type>
tlsRandSeeds(std::size_t const seed_cnt);

[[nodiscard]] PodBuffer<std::random_device::result_type> tlsRandSeeds();

[[nodiscard]] std::default_random_engine& tlsRandEngine();

template<IsAnyType T, ProbDist>
struct RandGeneratorTraits final
{
    static_assert(AlwaysFalse<T>);
};

template<IsRawIntegral Number_>
struct RandGeneratorTraits<Number_, ProbDist::K_INTEGRAL_UNIFORM> final
{
    using type = std::uniform_int_distribution<Number_>;
};

template<IsRawFloatingPoint Number_>
struct RandGeneratorTraits<Number_, ProbDist::K_REAL_UNIFORM> final
{
    using type = std::uniform_real_distribution<Number_>;
};

template<IsRawFloatingPoint Number_>
struct RandGeneratorTraits<Number_, ProbDist::K_NORMAL> final
{
    using type = std::normal_distribution<Number_>;
};

template<IsRawArithmetic Number_, ProbDist K_PROB_DIST_>
using RandGenerator = typename RandGeneratorTraits<Number_, K_PROB_DIST_>::type;

template<IsRawArithmetic Number_, ProbDist K_PROB_DIST_>
class BasicRandGenerator
{
    RandGenerator<Number_, K_PROB_DIST_> generator_{};

public:
    using result_type = Number_;

    [[nodiscard]] constexpr static auto min()
    {
        return stx::min<Number_>();
    }

    [[nodiscard]] constexpr static auto max()
    {
        return stx::max<Number_>();
    }

    BasicRandGenerator() : generator_(min(), max())
    {}

    /*
    If prob_dist is uniform distribution, then (arg1, arg2) are
    [lower_bound, upper_bound];

    If prob_dist is K_NORMAL distribution, then (arg1, arg2) are
    (mean, stddev).
	*/
    BasicRandGenerator(Number_ const arg1, Number_ const arg2)
        : generator_(arg1, arg2)
    {}

    [[nodiscard]] Number_ operator()()
    {
        return this->generator_(tlsRandEngine());
    }
};

using UniformI8RndGen =
    BasicRandGenerator<std::int8_t, ProbDist::K_INTEGRAL_UNIFORM>;
using UniformU8RndGen =
    BasicRandGenerator<std::uint8_t, ProbDist::K_INTEGRAL_UNIFORM>;

using UniformI16RndGen =
    BasicRandGenerator<std::int16_t, ProbDist::K_INTEGRAL_UNIFORM>;
using UniformU16RndGen =
    BasicRandGenerator<std::uint16_t, ProbDist::K_INTEGRAL_UNIFORM>;

using UniformI32RndGen =
    BasicRandGenerator<std::int32_t, ProbDist::K_INTEGRAL_UNIFORM>;
using UniformU32RndGen =
    BasicRandGenerator<std::uint32_t, ProbDist::K_INTEGRAL_UNIFORM>;

using UniformI64RndGen =
    BasicRandGenerator<std::int64_t, ProbDist::K_INTEGRAL_UNIFORM>;
using UniformU64RndGen =
    BasicRandGenerator<std::uint64_t, ProbDist::K_INTEGRAL_UNIFORM>;

using UniformRealRndGen = BasicRandGenerator<double, ProbDist::K_REAL_UNIFORM>;

using NormalRealRndGen = BasicRandGenerator<double, ProbDist::K_NORMAL>;

inline auto& randomize(IsMemArea auto& mm)
{
    auto [buf, size] = getMemAreaInfo(mm);
    randomize(buf, size);
    return mm;
}

class Uuid final : public ByteArray<16>
{
    using Base_ = ByteArray<16>;

public:
    using Base_::Base_;
};

[[nodiscard]] std::string genUuidString(SbLowercase const sb_lowercase);
[[nodiscard]] std::string genUuidString();

template<IsRawIntegral Number_>
struct TlsUniformIntRandGenerator final
{
    using result_type = Number_;

    [[nodiscard]] static constexpr Number_ min()
    {
        return std::numeric_limits<Number_>::min();
    }

    [[nodiscard]] static constexpr Number_ max()
    {
        return std::numeric_limits<Number_>::max();
    }

    [[nodiscard]] Number_ operator()() const noexcept
    {
        thread_local auto tls_rnd_gen =
            BasicRandGenerator<Number_, ProbDist::K_INTEGRAL_UNIFORM>(min(),
                                                                      max());

        return tls_rnd_gen();
    }
};

// auto a = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
// ranges::shuffle(v, tlsRandGenerator());
[[nodiscard]] TlsUniformIntRandGenerator<std::uint64_t>& tlsRandGenerator();

template<IsRandomAccessRange RandomAccessRange_>
requires EXPR(!IsConst<RandomAccessRange_&>)
inline void shuffle(RandomAccessRange_& rng)
{
    ranges::shuffle(rng, tlsRandGenerator());
}

template<IsRawArithmetic T>
[[nodiscard]] inline T rand(T const a, T const b)
{
    if (a > b) [[unlikely]]
    {
        RUNTIME_ERROR("Invalid range: ["sv, a, ", "sv, b, "]"sv);
    }

    if (a == b) [[unlikely]]
    {
        return a;
    }

    if constexpr (IsIntegral<T>)
    {
        return BasicRandGenerator<T, ProbDist::K_INTEGRAL_UNIFORM>(a, b)();
    }
    else
    {
        return BasicRandGenerator<T, ProbDist::K_REAL_UNIFORM>(a, b)();
    }
}

template<IsRawIntegral T>
[[nodiscard]] inline T rand()
{
    return rand(min<T>(), max<T>());
}

[[nodiscard]] Buffer      genRandBuffer(std::size_t const min_len,
                                        std::size_t const max_len);
[[nodiscard]] std::string genRandString(std::size_t const min_len,
                                        std::size_t const max_len);
} // namespace stx